package com.nulstudio.cloud.inventory.service;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.nulstudio.cloud.common.domain.vo.CommodityVo;
import com.nulstudio.cloud.common.domain.vo.SupplierVo;
import com.nulstudio.cloud.common.domain.vo.WarehouseVo;
import com.nulstudio.cloud.common.entity.NulInventory;
import com.nulstudio.cloud.common.exception.NulException;
import com.nulstudio.cloud.common.exception.NulExceptionConstants;
import com.nulstudio.cloud.inventory.client.CommodityClient;
import com.nulstudio.cloud.inventory.client.SupplierClient;
import com.nulstudio.cloud.inventory.client.WarehouseClient;
import com.nulstudio.cloud.inventory.domain.dto.TransferInventoryDto;
import com.nulstudio.cloud.inventory.domain.vo.InventoryVo;
import com.nulstudio.cloud.inventory.mapper.InventoryMapper;
import feign.FeignException;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;

@Service
public final class InventoryService {

    private final Logger log = LoggerFactory.getLogger(InventoryService.class);

    @Resource
    @NonNull
    private InventoryMapper inventoryMapper;

    @Resource
    @NonNull
    private WarehouseClient warehouseClient;

    @Resource
    @NonNull
    private CommodityClient commodityClient;

    @Resource
    @NonNull
    private SupplierClient supplierClient;

    @Nullable
    private WarehouseVo getWarehouseById(int id) {
        WarehouseVo warehouse = null;
        try {
            warehouse = warehouseClient.getWarehouseById(id).getResult();
        } catch (FeignException e) {
            if (e.status() != HttpStatus.NOT_FOUND.value()) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR, e.getMessage());
            }
        }
        return warehouse;
    }

    @Nullable
    private CommodityVo getCommodityById(int id) {
        CommodityVo commodity = null;
        try {
            commodity = commodityClient.getCommodityById(id);
        } catch (FeignException e) {
            if (e.status() != HttpStatus.NOT_FOUND.value()) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        }
        return commodity;
    }

    @Nullable
    private SupplierVo getSupplierById(int id) {
        SupplierVo supplier = null;
        try {
            supplier = supplierClient.getSupplierById(id);
        } catch (FeignException e) {
            if (e.status() != HttpStatus.NOT_FOUND.value()) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        }
        return supplier;
    }

    @NonNull
    public PageInfo<InventoryVo> fetchAll(int warehouseId, @NonNull String name, int page, int pageSize) {
        final PageInfo<InventoryVo> result;
        if (getWarehouseById(warehouseId) == null) {
            throw new NulException(NulExceptionConstants.WAREHOUSE_NOT_EXIST);
        }
        try (
                final Page<InventoryVo> ignored = PageHelper.startPage(page, pageSize);
        ) {
            final List<InventoryVo> inventoryVoList = inventoryMapper.fetchInventory(warehouseId, name);
            result = new PageInfo<>(inventoryVoList);
        } catch (Exception exception) {
            log.error("Error", exception);
            throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR, exception.getMessage());
        }
        return result;
    }

    public void stock(int warehouseId, int commodityId, int supplierId, @NonNull BigDecimal quantity) {
        if (getWarehouseById(warehouseId) == null) {
            throw new NulException(NulExceptionConstants.WAREHOUSE_NOT_EXIST);
        }
        if (getCommodityById(commodityId) == null) {
            throw new NulException(NulExceptionConstants.COMMODITY_NOT_EXIST);
        }
        if (getSupplierById(supplierId) == null) {
            throw new NulException(NulExceptionConstants.SUPPLIER_NOT_EXIST);
        }

        final NulInventory inventory = inventoryMapper.findInventory(warehouseId, commodityId, supplierId);
        if (inventory == null) {
            final NulInventory updatedInventory = new NulInventory(warehouseId, commodityId, supplierId, quantity);
            final int result = inventoryMapper.insert(updatedInventory);
            if (result <= 0) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        } else {
            final BigDecimal updatedQuantity = inventory.getQuantity().add(quantity);
            final NulInventory updatedInventory = new NulInventory(
                    inventory.getId(), warehouseId, commodityId, supplierId, updatedQuantity
            );
            final int result = inventoryMapper.update(updatedInventory);
            if (result <= 0) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        }
    }

    public void transfer(int warehouseId, @NonNull TransferInventoryDto transferInventoryDto) {
        for (final TransferInventoryDto.TransferInventoryEntry entry : transferInventoryDto.transferInventories()) {
            transfer(warehouseId, entry.id(), transferInventoryDto.destination(), entry.quantity());
        }
    }

    public void transfer(int warehouseId, int id, int destination, @NonNull BigDecimal quantity) {
        // 1. Check params.
        if (getWarehouseById(warehouseId) == null || getWarehouseById(destination) == null) {
            throw new NulException(NulExceptionConstants.WAREHOUSE_NOT_EXIST);
        }
        final NulInventory inventory = inventoryMapper.findInventoryById(id);
        if (inventory == null) {
            throw new NulException(NulExceptionConstants.INVENTORY_NOT_EXIST);
        }
        if (quantity.compareTo(BigDecimal.ZERO) <= 0 || quantity.compareTo(inventory.getQuantity()) > 0) {
            throw new NulException(NulExceptionConstants.INVALID_QUANTITY);
        }

        // 2. Operations for origin warehouse.
        if (quantity.equals(inventory.getQuantity())) {
            inventoryMapper.delete(warehouseId, List.of(id));
        } else {
            final BigDecimal updatedOriginQuantity = inventory.getQuantity().subtract(quantity);
            final NulInventory updatedInventory = new NulInventory(inventory.getId(), inventory.getWarehouseId(),
                    inventory.getCommodityId(), inventory.getSupplierId(), updatedOriginQuantity);
            final int result = inventoryMapper.update(updatedInventory);
            if (result <= 0) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        }

        // 3. Operations for destination warehouse.
        final NulInventory destinationInventory = inventoryMapper.findInventory(
                destination, inventory.getCommodityId(), inventory.getSupplierId()
        );
        if (destinationInventory == null) {
            final NulInventory updatedInventory = new NulInventory(
                    destination, inventory.getCommodityId(), inventory.getSupplierId(), quantity
            );
            final int result = inventoryMapper.insert(updatedInventory);
            if (result <= 0) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        } else {
            final BigDecimal updateDestinationQuantity = destinationInventory.getQuantity().add(quantity);
            final NulInventory updatedInventory = new NulInventory(
                    destinationInventory.getId(), destinationInventory.getWarehouseId(), destinationInventory.getCommodityId(),
                    destinationInventory.getSupplierId(), updateDestinationQuantity
            );
            final int result = inventoryMapper.update(updatedInventory);
            if (result <= 0) {
                throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
            }
        }
    }

    public void delete(int warehouseId, int id) {
        final int result = inventoryMapper.delete(warehouseId, List.of(id));
        if (result <= 0) {
            throw new NulException(NulExceptionConstants.INVENTORY_NOT_EXIST);
        }
    }

    public void delete(int warehouseId, @NonNull List<Integer> id) {
        final int result = inventoryMapper.delete(warehouseId, id);
        if (result <= 0) {
            throw new NulException(NulExceptionConstants.INVENTORY_NOT_EXIST);
        }
    }
}
